
abstract class Graph2D
{
  public int X;
  public int Y;
  public int W;
  public int H;

  Graph2D ()
  {
    reset ();  
  }

  Graph2D (int X, int Y, int W, int H)
  {
    this.X = X;
    this.Y = Y;
    this.W = W;
    this.H = H;
  }

  void reset ()
  {

  }

  abstract void draw ();

  //Generic function to test pointer in Bounds
  boolean PointerInBounds(int X, int Y)
  {
    int x = this.X;
    int y = this.Y;
    int w = this.W;
    int h = this.H;

    int x1 = x + w; 
    int y1 = y + h;
    int temp;

    if(y > y1)
    {
      temp = y;
      y = y1;
      y1 = temp;
    }
    if(x > x1)
    {
      temp = x;
      x = x1;
      x1 = temp;    
    }
    return ((X >= x && X <= x1) && (Y >= y && Y <= y1));
  }
}

class GraphScatter extends Graph2D
{
  int pxIndexX;    // nustn't be more than ScaleUnitX * ScaleSizeX
  int ScaleUnitX;   // ? Px/Unit  
  int ScaleUnitY;

  Button btnToggleY;
  boolean toggleScatter;
  int hover;

  ArrayList DataList;
  ArrayList node;

  int _debug_DataList;

  /** Local Mouse **/
  int lMouseX; 
  int lMouseY;

  GTimeLine gtl ;

  GraphScatter (HashMap data, int X, int Y, int W, int H)
  {
    /** Initialize the Base Class **/
    super (X, Y, W, H);

    /** Get the Data; sorted **/
    DataList = new ArrayList(data.values());
    Collections.sort(DataList);

    /** Make a new toggle button: toggles between the graphs **/
    btnToggleY = new Button(ID_BTN_TOGGLEX," ",110,-12,20,20);
    btnToggleY.setColor(0x99555555);
    
    _debug_DataList = 0;

    /** Array list holds all the drawable nodes **/
    node = new ArrayList();

    /** TimeLine Purpose : ToDo Hack**/
    //Date d = new Date(109,07,15,00,00);
    Date d = ((SitesByTime)DataList.get(0)).date;

    gtl = new GTimeLine(d, d, d);

    /** Sync the timeline and Graph **/
    pxIndexX = gtl.pScale;
    ScaleUnitX = gtl.dScaleX;
    ScaleUnitY = 20;
  }

  void draw()
  {
    fill(0xFFFFFFFF);
    textFont(_font, 12);
    text("Show Hours/Day:", 10,100);
    
    preDraw();
    gtl.draw();
  }

  void preDraw()
  {
    pushMatrix ();
    translate (X, Y);

    startDate = ((SitesByTime)DataList.get(0)).date;

    /** Find the bottomscale and Width **/
    int scaleBase = H - MARGIN_GRAPH_Y - DIM_SCALE_H;
    int scaleWidth = W - MARGIN_GRAPH_X*2;

    strokeWeight(1);
    stroke(0xFF<<24 | STK_SCALE_BG);
    fill(0xFFFFFFFF);

    if(!toggleScatter)
    {
      /** Y-AXIS **/
      for(int i = 0; i < 24; i ++)
      {
        line(0, scaleBase - i*ScaleUnitY, 5, scaleBase - i*ScaleUnitY);
        text(i+":00", 20, scaleBase - i*ScaleUnitY+5);
      }

      /** Funky timezone color **/
      noStroke();
      fill(0x33000033);
      rect(0, scaleBase - ScaleUnitY*-1, W, - ScaleUnitY*( 3 - (-1) ));
      rect(0, scaleBase - ScaleUnitY*21, W, - ScaleUnitY*( 24 - 21 ));
      fill(0x33003300);
      rect(0, scaleBase - ScaleUnitY*3, W, - ScaleUnitY*( 9 - 3 ));
      fill(0x33AAAA00);
      rect(0, scaleBase - ScaleUnitY*9, W, - ScaleUnitY*( 15 - 9));
      fill(0x33AA0000);
      rect(0, scaleBase - ScaleUnitY*15, W, - ScaleUnitY*(21 - 15));
    }
    btnToggleY.setColor((toggleScatter)?0x99666666:0x99FFFFFF);
    btnToggleY.draw();

    /** Plot the data **/

    if(!toggleScatter)
    {
      Node n;
      int selectedNode = -1;
      for(int i = 0; i < node.size(); i ++)
      {
        n = (Node)node.get(i);
        /** Ploting Circles **/
        //fill(0x33FFFFFF);
        n.setColor(0x55FFFFFF);
        if(n.over)n.setColor(0x33FF0000);
        ((Node)node.get(i)).draw();

        if(n.over)
        {
          selectedNode = i;
        }
      }
      if(selectedNode != -1)
        ((Node)node.get(selectedNode)).drawInfo();
    }
    else
    {      
      int totalCount = 0;
      Date pdate = ((SitesByTime)DataList.get(0)).date;

      for(int i = 0; i < DataList.size(); i ++)
      {
        totalCount +=  ((SitesByTime)DataList.get(i)).totalCountThisHour;
        if(pdate.getDate() != ((SitesByTime)DataList.get(i)).date.getDate())
        {
          //println( pdate.getDate() + " - " + totalCount );
          fill(0xFF888888);
          rect(gtl.getX(((SitesByTime)DataList.get(i)).date), 500, gtl.dScaleX, -totalCount/4);
          pushStyle();
          textFont(_font, 12);
          //fill();
          text(totalCount, gtl.getX(((SitesByTime)DataList.get(i)).date)+15, 500-totalCount/4);
          popStyle();
          totalCount = 0;
        }
        pdate = ((SitesByTime)DataList.get(i)).date;
      }
      //fill(0xFF999999);
      //rect(gtl.getX(pdate), 500, gtl.dScaleX, -totalCount/4);
    }
    popMatrix();
  }

  /******************************/
  /** Properties and Synthesis **/
  /******************************/
  void mouseOver()
  {
    lMouseX = mouseX - X;
    lMouseY = mouseY - Y;

    if(btnToggleY.PointerInBounds(lMouseX, lMouseY))
      hover = btnToggleY.id;

    for(int i = 0; i < node.size(); i ++)
    {
      ((Node)node.get(i)).over = false;
      if(((Node)node.get(i)).PointerInBounds (lMouseX, lMouseY))
        ((Node)node.get(i)).over = true;
    }
    gtl.mouseMoved();
  }

  void mouseDragged()
  {
    //pxIndexX = pxIndexX - (pmouseX - mouseX);
    gtl.mouseDragged();

    ScaleUnitX = gtl.dScaleX;
    pxIndexX = gtl.pScale;
    updateNode();
  }

  void mouseWheel(int delta)
  {
    gtl.mouseWheel(delta);

    ScaleUnitX = gtl.dScaleX;
    pxIndexX = gtl.pScale;
    updateNode();
  }

  void mouseClicked()
  {
    if(btnToggleY.PointerInBounds(lMouseX, lMouseY))
      toggleScatter = !toggleScatter;
  }

  void mousePressed()
  {
    gtl.mousePressed();
  }

  void keyPressed()
  {
    if (key == CODED) 
    {
      if (keyCode == UP) 
      {
        scatterGraph._debug_DataList ++;
      } 
      else if (keyCode == DOWN) 
      {
        scatterGraph._debug_DataList --;
      } 
    }
    updateNode();
  }

  /** Update Node **/
  void updateNode()
  {
    /** Debug stuff **/
    if(_debug_DataList > DataList.size())
      _debug_DataList = DataList.size();
    if(_debug_DataList < 0)
      _debug_DataList = 0;

    int scaleBase = H - MARGIN_GRAPH_Y - DIM_SCALE_H;
    int scaleWidth = W - MARGIN_GRAPH_X*2;

    node = new ArrayList();
    for(int i = 0; i < DataList.size(); i ++)
    {
      SitesByTime s = (SitesByTime)DataList.get(i);

      long x = ( (s.date.getTime()-s.date.getHours()*60*60*1000) - (startDate.getTime() - startDate.getHours()*60*60*1000) )/ (24*60*60*1000);
      if(pxIndexX + x * ScaleUnitX < 0 || pxIndexX + x * ScaleUnitX > W)
        continue;
      if(pxIndexX + x * ScaleUnitX > W)
        continue;

      int xxxx = gtl.getX(s.date);
      node.add(new Node((int )(pxIndexX + x * ScaleUnitX), scaleBase - s.date.getHours() * ScaleUnitY, s.totalCountThisHour/5 , s.totalCountThisHour/5, s.totalCountThisHour, s.getSitesByHour()));
    }
  }
  /******************************/
  /** Properties and Synthesis **/
  /******************************/

  /** Title: **/
  String szGraphTitle;

  void setGraphTitle (String val)
  {
    this.szGraphTitle = val;
  }
  String getGraphTitle ()
  {
    return this.szGraphTitle;
  }


  String szGraphInfo;

  void setGraphInfo (String val)
  {
    this.szGraphInfo = val;
  }
  String getGraphInfo ()
  {
    return this.szGraphInfo;
  }
}


class Node
{
  int X;
  int Y;
  int W;
  int H;
  int clr;
  boolean over;

  int totalCount;
  ArrayList siteCount;

  Node(int X, int Y, int W, int H, int tc, ArrayList sc)
  {
    this.X = X;
    this.Y = Y;
    this.W = W;
    this.H = H;
    this.over = false;  
    this.totalCount = tc;
    this.siteCount = sc;
  }

  void draw()
  {
    fill(clr);
    ellipse (X, Y, W, H);
  }

  void drawInfo()
  {
    int fw = 300;
    int fh = 25+min(siteCount.size(),10)*20;

    int newX = (X+fw > APP_WIDTH)? X-fw : X;
    int newY = (Y+fh > (APP_HEIGHT - 200))? Y-fh : Y;
    pushStyle();
    fill(0x33FFFFFF);
    roundRect(10, 0xFF444444, 0xFF444444, 0, newX, newY, fw, fh);

    fill(0xFFFFFFFF);
    textFont(_font, 18);
    textAlign(LEFT);
    //fill()
    text("Total Sites Visited : " + totalCount, newX+20, newY+15);


    textFont(_font, 12);
    Collections.sort(siteCount);
    //println(n.siteCount.totalCountThisHour);
    for(int j = 0; j<min(siteCount.size(),10); j++)
    {
      SiteCountHourly sch = (SiteCountHourly)siteCount.get(j);
      //println(sch.site+" "+sch.countHourly);  
      text(+sch.countHourly+TAB+": "+sch.site , newX+20, newY+35+(20*j));
    }
    popStyle();
  }

  void setColor(int clr)
  {
    this.clr = clr;
  }

  //Generic function to test pointer in Bounds
  int prox = 7;
  boolean PointerInBounds(int mX, int mY)
  {
    return ((mX >= X-prox && mX <= X+prox) && (mY >= Y-prox && mY <= Y+prox));
  }
}






